import java.util.List;
import java.util.LinkedList;

public class TinyGA {
	public TinyGA() {;}

	public abstract class Sent {

		private Atributo<Error> err;
		private Atributo<List<Instruccion>> cod;

		protected abstract Error err_exp();
		protected abstract List<Instruccion> cod_exp();

		protected Sent() {

			err = new Atributo<Error>();
			err.fijaExpresion(new ExpSem<Error>() {
				public Error val() {
					return err_exp();
				}

			});

			cod = new Atributo<List<Instruccion>>();
			cod.fijaExpresion(new ExpSem<List<Instruccion>>() {

				public List<Instruccion> val() {
					return cod_exp();
				}

			});

		}

		public Atributo<Error> err() {
			return err;
		}

		public Atributo<List<Instruccion>> cod() {
			return cod;
		}

	}

	public abstract class Decs {

		private Atributo<Error> err;
		private Atributo<TS> ts;

		protected abstract Error err_exp();
		protected abstract TS ts_exp();

		protected Decs() {

			err = new Atributo<Error>();
			err.fijaExpresion(new ExpSem<Error>() {

				public Error val() {
					return err_exp();
				}

			});

			ts = new Atributo<TS>();
			ts.fijaExpresion(new ExpSem<TS>() {

				public TS val() {
					return ts_exp();
				}

			});

		}

		public Atributo<Error> err() {
			return err;
		}

		public Atributo<TS> ts() {
			return ts;
		}

	}

	public abstract class Dec {

		private Atributo<String> cte;
		private Atributo<String> val;
		private Atributo<Integer> fila;
		private Atributo<Integer> col;

		protected abstract String cte_exp();
		protected abstract String val_exp();
		protected abstract Integer fila_exp();
		protected abstract Integer col_exp();

		protected Dec() {

			cte = new Atributo<String>();
			cte.fijaExpresion(new ExpSem<String>() {

				public String val() {
					return cte_exp();
				}

			});

			val = new Atributo<String>();
			val.fijaExpresion(new ExpSem<String>() {

				public String val() {
					return val_exp();
				}

			});

			fila = new Atributo<Integer>();
			fila.fijaExpresion(new ExpSem<Integer>() {

				public Integer val() {
					return fila_exp();
				}

			});

			col = new Atributo<Integer>();
			col.fijaExpresion(new ExpSem<Integer>() {

				public Integer val() {
					return col_exp();
				}

			});
		}

		public Atributo<String> cte() {
			return cte;
		}

		public Atributo<String> val() {
			return val;
		}

		public Atributo<Integer> fila() {
			return fila;
		}

		public Atributo<Integer> col() {
			return col;
		}
	}

	abstract public class Exp {

		private ExpCtx ctx;

		private Atributo<TS> tsh;

		private Atributo<Error> err;
		private Atributo<List<Instruccion>> cod;

		protected abstract Error err_exp();
		protected abstract List<Instruccion> cod_exp();

		protected Exp() {

			tsh = new Atributo<TS>();
			tsh.fijaExpresion(new ExpSem<TS>() {

				public TS val() {
					return ctx.tsh_exp();
				}

			});

			err = new Atributo<Error>();
			err.fijaExpresion(new ExpSem<Error>() {

				public Error val() {
					return err_exp();
				}

			});

			cod = new Atributo<List<Instruccion>>();
			cod.fijaExpresion(new ExpSem<List<Instruccion>>() {

				public List<Instruccion> val() {
					return cod_exp();
				}

			});

		}

		public Atributo<TS> tsh() {
			return tsh;
		}

		public Atributo<Error> err() {
			return err;
		}

		public Atributo<List<Instruccion>> cod() {
			return cod;
		}

		public void registraCtx(ExpCtx ctx) {
			this.ctx = ctx;
		}

	}

	public interface ExpCtx {
		public TS tsh_exp();
	}

	abstract public class Exp0 extends Exp {;}
	abstract public class Exp1 extends Exp {;}

	public class SentR1 extends Sent {
		/**
		 * Sent ::= Decs & Exp0
		 *		
		 *		Exp0.tsh = Decs.ts
		 * 
		 *		Sent.err = Decs.err ++ Exp0.err
		 *		Sent.cod = Exp0.cod
		 */
		private Decs decs;
		private Exp0 exp0;

		public SentR1(Decs decs, Exp0 exp0) {
			super();
			this.decs = decs;
			this.exp0 = exp0;

			exp0.registraCtx(new ExpCtx() {

				public TS tsh_exp() {
					return SentR1.this.decs.ts().val();
				}

			});

			err().ponDependencias(decs.err(), exp0.err());
			cod().ponDependencias(exp0.cod());
			exp0.tsh().ponDependencias(decs.ts());

		}

		protected final Error err_exp() {
			return joinErrors(decs.err().val(), exp0.err().val());
		}

		protected final List<Instruccion> cod_exp() {
			return exp0.cod().val();
		}

	}

	public class SentR1Debug extends SentR1 {
		private final static String REGLA = "Sent ::= Decs & Exp0";

		public SentR1Debug(Decs decs, Exp0 exp0) {
			super(decs, exp0);
			err().fijaDescripcion(REGLA + "|Sent.err");
			cod().fijaDescripcion(REGLA + "|Sent.cod");
			exp0.tsh().fijaDescripcion(REGLA + "|Exp0.tsh");
		}
	}

	public class DecsR1 extends Decs {
		/**
		 * Decs ::= Decs ; Dec
		 * 
		 *		Decs(0).ts = aniadeCte(Decs(1).ts, Dec.cte.lex, Dec.val)
		 *		Decs(0).err = Decs(1).err ++ errorSi(estaEn(Dec.cte, Decs(1).ts), 
		 *				Dec.fila, Dec.col, "Constante duplicada: " + Dec.cte)
		 */
		private Decs decs_1;
		private Dec dec;

		public DecsR1(Decs decs_1, Dec dec) {
			super();
			this.decs_1 = decs_1;
			this.dec = dec;

			ts().ponDependencias(decs_1.ts(), dec.cte(), dec.val());
			err().ponDependencias(decs_1.ts(), decs_1.err(), dec.cte(),
					dec.fila(), dec.col());

		}

		protected final TS ts_exp() {
			return aniadeCte(decs_1.ts().val(), dec.cte().val(), dec.val()
					.val());
		}

		protected final Error err_exp() {
			return joinErrors(
					decs_1.err().val(),
					errorSi(estaEn(dec.cte().val(), decs_1.ts().val()), dec
							.fila().val(), dec.col().val(),
							"Constante duplicada:" + dec.cte().val()));
		}

	}

	public class DecsR1Debug extends DecsR1 {
		private final static String REGLA = "Decs ::= Decs ; Dec";

		public DecsR1Debug(Decs decs_1, Dec dec) {
			super(decs_1, dec);
			ts().fijaDescripcion(REGLA + "|Decs(0).ts");
			err().fijaDescripcion(REGLA + "|Decs(0).err");
		}
	}

	public class DecsR2 extends Decs {
		/**
		 * Decs ::= Dec
		 * 
		 *		Decs.ts = aniadeCte(creaTS(), Dec.cte, Dec.val)
		 *		Decs.err = noError()
		 */
		private Dec dec;

		public DecsR2(Dec dec) {
			super();
			this.dec = dec;
			ts().ponDependencias(dec.cte(), dec.val());
		}

		protected final TS ts_exp() {
			return aniadeCte(creaTS(), dec.cte().val(), dec.val().val());
		}

		protected final Error err_exp() {
			return noError();
		}
	}

	public class DecsR2Debug extends DecsR2 {
		private final static String REGLA = "Decs::=Dec";

		public DecsR2Debug(Dec dec) {
			super(dec);
			ts().fijaDescripcion(REGLA + "|Decs.ts");
			err().fijaDescripcion(REGLA + "|Decs.err");
		}
	}

	public class DecR1 extends Dec {
		/**
		 * Dec ::= id = num
		 *
		 *		Dec.cte = id.lex
		 *		Dec.val = num.lex
		 *		Dec.fila = id.fila
		 *		Dec.col = id.col
		 */
		private Token id;
		private Token num;

		public DecR1(Token id, Token num) {
			super();
			this.id = id;
			this.num = num;
		}

		protected final String cte_exp() {
			return id.lex();
		}

		protected final String val_exp() {
			return num.lex();
		}

		protected final Integer fila_exp() {
			return new Integer(id.fila());
		}

		protected final Integer col_exp() {
			return new Integer(id.col());
		}

	}

	public class DecR1Debug extends DecR1 {
		private final static String REGLA = "Dec ::= id = num";

		public DecR1Debug(Token id, Token num) {
			super(id, num);
			cte().fijaDescripcion(REGLA + "|Dec.cte");
			val().fijaDescripcion(REGLA + "|Dec.val");
			fila().fijaDescripcion(REGLA + "|Dec.fila");
			col().fijaDescripcion(REGLA + "|Dec.col");
		}
	}

	public class Exp0R1 extends Exp0 {
		/**
		 * Exp0 ::= Exp0 + Exp1
		 * 
		 *		Exp0(1).tsh = Exp0(0).tsh
		 * 
		 *		Exp1.tsh = Exp0(0).tsh
		 *
		 * 		Exp0(0).err = Exp0(1).err ++ Exp1.err
		 * 		Exp0(0).cod =	Exp0(1).cod ||
		 * 						Exp1.cod || 
		 * 						suma
		 */
		private Exp0 exp0_1;
		private Exp1 exp1;

		public Exp0R1(Exp0 exp0_1, Exp1 exp1) {

			this.exp0_1 = exp0_1;
			this.exp1 = exp1;

			exp0_1.registraCtx(new ExpCtx() {
				public TS tsh_exp() {
					return Exp0R1.this.tsh().val();
				}
			});

			exp1.registraCtx(new ExpCtx() {
				public TS tsh_exp() {
					return Exp0R1.this.tsh().val();
				}
			});

			err().ponDependencias(exp0_1.err(), exp1.err());
			cod().ponDependencias(exp0_1.cod(), exp1.cod());
			exp0_1.tsh().ponDependencias(tsh());
			exp1.tsh().ponDependencias(tsh());
		}

		protected final Error err_exp() {
			return joinErrors(exp0_1.err().val(), exp1.err().val());
		}

		protected final List<Instruccion> cod_exp() {
			return concat(exp0_1.cod().val(), concat(exp1.cod().val(), suma()));
		}
	}

	public class Exp0R1Debug extends Exp0R1 {
		private final static String REGLA = "Exp0 ::= Exp0 + Exp1";

		public Exp0R1Debug(Exp0 exp0_1, Exp1 exp1) {
			super(exp0_1, exp1);
			err().fijaDescripcion(REGLA + "|Exp0(0).err");
			cod().fijaDescripcion(REGLA + "|Exp0(0).cod");
			exp0_1.tsh().fijaDescripcion(REGLA + "|Exp0(1).tsh");
			exp1.tsh().fijaDescripcion(REGLA + "|Exp1.tsh");
		}
	}

	public class Exp0R2 extends Exp0 {
		/**
		 * Exp0 ::= Exp1 * Exp1
		 * 
		 * 		Exp1(0).tsh = Exp0.tsh
		 * 
		 * 		Exp1(1).tsh = Exp0.tsh
		 * 
		 *		Exp0.err = Exp1(0).err ++ Exp1(1).err
		 *		Exp0.cod =	Exp1(0).cod ||
		 * 					Exp1(1).cod ||
		 * 					mul
		 */
		private Exp1 exp1_0;
		private Exp1 exp1_1;
		
		public Exp0R2(Exp1 exp1_0, Exp1 exp1_1) {

			this.exp1_0 = exp1_0;
			this.exp1_1 = exp1_1;

			exp1_0.registraCtx(new ExpCtx() {
				public TS tsh_exp() {
					return Exp0R2.this.tsh().val();
				}
			});

			exp1_1.registraCtx(new ExpCtx() {
				public TS tsh_exp() {
					return Exp0R2.this.tsh().val();
				}
			});

			err().ponDependencias(exp1_0.err(), exp1_1.err());
			cod().ponDependencias(exp1_0.cod(), exp1_1.cod());
			exp1_0.tsh().ponDependencias(tsh());
			exp1_1.tsh().ponDependencias(tsh());
		}

		protected final Error err_exp() {
			return joinErrors(exp1_0.err().val(), exp1_1.err().val());
		}

		protected final List<Instruccion> cod_exp() {
			return concat(concat(exp1_0.cod().val(), exp1_1.cod().val()), mul());
		}
	}

	public class Exp0R2Debug extends Exp0R2 {
		private final static String REGLA = "Exp0 ::= Exp1 * Exp1";

		public Exp0R2Debug(Exp1 exp1_0, Exp1 exp1_1) {
			super(exp1_0, exp1_1);
			err().fijaDescripcion(REGLA + "|Exp0.err");
			cod().fijaDescripcion(REGLA + "|Exp0.cod");
			exp1_0.tsh().fijaDescripcion(REGLA + "|Exp1(0).tsh");
			exp1_1.tsh().fijaDescripcion(REGLA + "|Exp1(1).tsh");
		}
	}

	/**
	 *	Exp0 ::= Exp1
	 *
	 *		Exp1.tsh = Exp0.tsh
	 *		Exp0.err = Exp1.err
	 *		Exp0.cod = Exp1.cod
	 */
	public class Exp0R3 extends Exp0 {
		public Exp0R3(Exp1 exp1) {
			this.exp1 = exp1;
			exp1.registraCtx(new ExpCtx() {
				public TS tsh_exp() {
					return Exp0R3.this.tsh().val();
				}
			});
			err().ponDependencias(exp1.err());
			cod().ponDependencias(exp1.cod());
			exp1.tsh().ponDependencias(tsh());
		}

		protected final Error err_exp() {
			return exp1.err().val();
		}

		public List<Instruccion> cod_exp() {
			return exp1.cod().val();
		}

		private Exp1 exp1;
	}

	public class Exp0R3Debug extends Exp0R3 {
		private final static String REGLA = "Exp0 ::= Exp1";

		public Exp0R3Debug(Exp1 exp1) {
			super(exp1);
			err().fijaDescripcion(REGLA + "|Exp0.err");
			cod().fijaDescripcion(REGLA + "|Exp0.cod");
			exp1.tsh().fijaDescripcion(REGLA + "|Exp1.tsh");
		}
	}

	public class Exp1R1 extends Exp1 {
		/**
		 *	Exp1 ::= num
		 *		
		 *		Exp1.err = noError()
		 *		Exp1.cod = apila(num.lex)
		 */
		private Token num;
		
		public Exp1R1(Token num) {
			this.num = num;
		}

		protected final Error err_exp() {
			return noError();
		}

		protected final List<Instruccion> cod_exp() {
			return single(apila(num.lex()));
		}
	}

	public class Exp1R1Debug extends Exp1R1 {
		private final static String REGLA = "Exp1 ::= num";

		public Exp1R1Debug(Token num) {
			super(num);
			err().fijaDescripcion(REGLA + " | Exp1.err");
			cod().fijaDescripcion(REGLA + " | Exp1.cod");
		}
	}

	public class Exp1R2 extends Exp1 {
		/**
		 * Exp1 ::= id
		 *
		 *	Exp1.err = errorSi(not estaEn(id.lex,Exp1.tsh), id.fila, id.col, "Identificador no declarado")
		 *	Exp1.cod = apila(valDe(id.lex, Exp1.tsh))
		 */
		private Token id;
		
		public Exp1R2(Token id) {
			this.id = id;
			cod().ponDependencias(tsh());
			err().ponDependencias(tsh());
		}

		protected final Error err_exp() {
			return errorSi(!estaEn(id.lex(), tsh().val()), id.fila(), id.col(),
					"Identificador no declarado:" + id.lex());
		}

		protected final List<Instruccion> cod_exp() {
			return single(apila(valDe(id.lex(), tsh().val())));
		}
	}

	public class Exp1R2Debug extends Exp1R2 {
		private final static String REGLA = "Exp1 ::= id";

		public Exp1R2Debug(Token num) {
			super(num);
			err().fijaDescripcion(REGLA + " | Exp1.err");
			cod().fijaDescripcion(REGLA + " | Exp1.cod");
		}
	}

	public class Exp1R3 extends Exp1 {
		/**
		 * Exp1 ::= (Exp0)
		 * 
		 * 	Exp0.tsh = Exp1.tsh
		 *
		 * 	Exp1.err = Exp0.err
		 * 	Exp1.cod = Exp0.cod
		 */
		private Exp0 exp0;
		
		public Exp1R3(Exp0 exp0) {

			this.exp0 = exp0;

			exp0.registraCtx(new ExpCtx() {

				public TS tsh_exp() {
					return Exp1R3.this.tsh().val();
				}

			});

			err().ponDependencias(exp0.err());
			cod().ponDependencias(exp0.cod());
			exp0.tsh().ponDependencias(tsh());
		}

		protected final Error err_exp() {
			return exp0.err().val();
		}

		public List<Instruccion> cod_exp() {
			return exp0.cod().val();
		}
	}

	public class Exp1R3Debug extends Exp1R3 {
		private final static String REGLA = "Exp1 ::= ( Exp0 )";

		public Exp1R3Debug(Exp0 exp0) {
			super(exp0);
			err().fijaDescripcion(REGLA + " | Exp1.err");
			cod().fijaDescripcion(REGLA + " | Exp1.cod");
			exp0.tsh().fijaDescripcion(REGLA + " | Exp0.tsh");
		}
	}

	public TS aniadeCte(TS ts, String cte, String val) {
		return ts.aniade(cte, val);
	}

	public boolean estaEn(String cte, TS ts) {
		return ts.estaEn(cte);
	}

	public String valDe(String cte, TS ts) {
		return ts.valDe(cte);
	}

	public TS creaTS() {
		return new TS();
	}

	public List<Instruccion> concat(List<Instruccion> c1, List<Instruccion> c2) {
		c1.addAll(c2);
		return c1;
	}

	public List<Instruccion> concat(List<Instruccion> c1, Instruccion i) {
		c1.add(i);
		return c1;
	}

	public Instruccion suma() {
		return Instruccion.nuevaISuma();
	}

	public Instruccion mul() {
		return Instruccion.nuevaIMul();
	}

	public Instruccion apila(String arg) {
		return Instruccion.nuevaIApila(arg);
	}

	public List<Instruccion> single(Instruccion i) {
		List<Instruccion> is = new LinkedList<Instruccion>();
		is.add(i);
		return is;
	}

	public Error noError() {
		return new Error();
	}

	public Error joinErrors(Error e1, Error e2) {
		return new Error(e1, e2);
	}

	public Error errorSi(boolean condicion, int fila, int col, String msg) {
		if (condicion) {
			return new Error("(" + fila + "," + col + ") ERROR CONTEXTUAL: "
					+ msg);
		} else {
			return new Error();
		}
	}

	public static void main(String[] args) {
		TinyGA ga = new TinyGA();

		TinyGA.Sent sent = ga.new SentR1(ga.new DecsR1(ga.new DecsR2(
				ga.new DecR1(new Token(CatLexica.ID, "x"), new Token(
						CatLexica.NUM, "5"))), ga.new DecR1(new Token(
				CatLexica.ID, "y"), new Token(CatLexica.NUM, "6"))),
				ga.new Exp0R1(ga.new Exp0R3(ga.new Exp1R2(new Token(
						CatLexica.ID, "y"))), ga.new Exp1R2(new Token(
						CatLexica.ID, "x"))));

// 		x = 5;
//		y = 6
//		&
//		y + x
		
		System.out.println(sent.err().val());
		System.out.println(sent.cod().val());
	}
}
